#include "servicemanager.h"
#include "connection.h"
#include "fileidmapper.h"
#include "fileevent.pb.h"

#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusConnectionInterface>
#include <QTimer>

#include <sys/socket.h>
#include <fcntl.h>
#include <linux/kdev_t.h>
#include <sys/stat.h>

#include <glib.h>
#include <mntent.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#include <google/protobuf/wire_format_lite.h>

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)
#define MAJOR_KERNEL(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR_KERNEL(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV_KERNEL(ma,mi)	(((ma) << MINORBITS) | (mi))

enum event_type {
    FILE_CREATE,
    FILE_DELETE,
    FILE_RENAME,
    FILE_CONTENT_CHANGED,
    FILE_PERM_CHANGE,
    MOUNT_ADDED,
    MOUNT_REMOVED
};

// 查询设备挂载路径
// 成功返回动态分配的挂载路径字符串，失败返回NULL
char* get_mountpoint_by_device(const char *device_path)
{
    if (!device_path) return NULL;

    char canonical_path[PATH_MAX];
    if (realpath(device_path, canonical_path) == NULL) {
        return NULL;
    }

    FILE *fp = setmntent("/proc/mounts", "r");
    if (!fp) return NULL;

    struct mntent *ent;
    char *result = NULL;
    char mnt_fsname_canonical[PATH_MAX];
    
    while ((ent = getmntent(fp)) != NULL) {
        if (realpath(ent->mnt_fsname, mnt_fsname_canonical) && 
            strcmp(mnt_fsname_canonical, canonical_path) == 0) {
            result = strdup(ent->mnt_dir);
            if (!result) break;
            break;
        }
    }

    endmntent(fp);
    return result;
}

ServiceManager::ServiceManager(QObject *parent)
    : QObject{parent}, QDBusContext()
{

}

void ServiceManager::initSystemBus()
{
    bool success = false;
    auto connection = QDBusConnection::connectToBus(QDBusConnection::SystemBus, "servicemanager");
    if (!connection.isConnected()) {
        qCritical() << "failed to connect system bus:" << connection.lastError();
    }

    success = connection.registerService("org.ukui.IndexFramework");
    if (!success) {
        qCritical() << "failed to register system bus service:" << connection.lastError();
    }
    success = connection.registerObject("/", "org.ukui.IndexFramework.Host.ServiceManager", this, QDBusConnection::ExportAdaptors);
    if (!success) {
        qCritical() << "failed to register system service object:" << connection.lastError();
    }
}

void ServiceManager::setupService()
{
    auto uid = connection().interface()->serviceUid(message().service());
    qDebug() << "do setup service, caller:" << uid;
    if (m_connections.contains(uid)) {
        qDebug() << "connection for" << uid << "has been setup";
        return;
    }
    qDebug() << "start setup service";
    if (!m_udevClient) {
        m_udevClient = g_udev_client_new(nullptr);
    }

    qDebug() << "start create fileid mapper";
    auto mapper = FileIDMapper::instance();

    qDebug() << "start query device infos";
    // 查询block子系统设备，对有ID_FS_UUID属性的设备进行处理，将其uuid、路径和dev_t存入缓存
    GList *devices = g_udev_client_query_by_subsystem(m_udevClient, "block");
    qDebug() << "get device infos list";
    if (devices) {
        for (GList *iter = devices; iter != NULL; iter = iter->next) {
            GUdevDevice *device = G_UDEV_DEVICE(iter->data);
            const char *uuid = g_udev_device_get_property(device, "ID_FS_UUID");
            if (!uuid) {continue;}
            dev_t dev = g_udev_device_get_device_number(device);
            auto deviceFile = g_udev_device_get_device_file(device);
            std::string devUUID = uuid;
            g_autofree char* res = get_mountpoint_by_device(deviceFile);
            std::string mountRootPath;
            // 如果是根目录，则设置挂载路径为空字符串，避免拼接出错。
            if (!res) {
                continue;
            } else if (g_str_equal(res, "/")) {
                mountRootPath = "";
            } else {
                QString str(res);
                mountRootPath = str.toStdString();
            }
            qDebug() << "init database" << uuid << dev << MAJOR(dev) << MINOR(dev) << MKDEV_KERNEL(MAJOR(dev), MINOR(dev)) << res;
            mapper->initDataBase(uuid);
            qDebug() << "init database done";
            std::cout << "start insert dev cache" << dev << uuid << mountRootPath << std::endl;
            m_devCache.insert(MKDEV_KERNEL(MAJOR(dev), MINOR(dev)), std::make_pair(uuid, mountRootPath));
            m_mountpointsCache.insert(QString::fromStdString(uuid), std::make_pair(mountRootPath, dev));
            std::cout << "insert dev cache done" << std::endl;
        }
        qDebug() << "free device list";
        g_list_free_full(devices, g_object_unref);
        qDebug() << "free device list done";
    }
    qDebug() << "query device infos done";

    int sockfd[2];
    int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
    if (ret == -1) {
        sendErrorReply(QDBusError::Failed, "failed to create socket pair");
        return;
    }
    fcntl(sockfd[0], F_SETFL, O_NONBLOCK);
    fcntl(sockfd[1], F_SETFL, O_NONBLOCK);

    int buf_size = 65536 * 2;  // 64 x 2KB

    setsockopt(sockfd[0], SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size));
    setsockopt(sockfd[0], SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size));
    setsockopt(sockfd[1], SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size));
    setsockopt(sockfd[1], SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size));

    qDebug() << "start setup user connection";
    auto conn = new Connection(uid, sockfd, this);
    m_connections.insert(uid, conn);
    qDebug() << "start setup service done";
}

struct query_request {
    char mount_root[256];
    unsigned long inode;
    char result[2048];
};
#define DEV_PATH "/dev/inode_query"
#define IOCTL_CMD 0xDEADBEEF

QString ServiceManager::queryFileFullpath(const QString &uuid, quint64 id)
{
    std::string mountRoot = m_mountpointsCache.value(uuid).first;

    if (!mountRoot.c_str()) {
        sendErrorReply(QDBusError::InvalidArgs, "invalid uuid");
        return nullptr;
    }

    int fd = open(DEV_PATH, O_RDWR);
    if (fd < 0) {
        sendErrorReply(QDBusError::Failed, QString("failed to open %1").arg(DEV_PATH));
        return nullptr;
    }

    auto mapper = FileIDMapper::instance();
    unsigned long inode = mapper->getFileInodeByID(uuid.toStdString(), id);

    struct query_request req;
    memcpy(req.mount_root, mountRoot.c_str(), sizeof(req.mount_root));
    req.inode = inode;

    if (ioctl(fd, IOCTL_CMD, &req) < 0) {
        sendErrorReply(QDBusError::Failed, QString("failed to exec ioctl %1").arg(IOCTL_CMD));
        close(fd);
        return nullptr;
    }

    close(fd);
    auto tmp = QString::fromStdString(mountRoot);
    if (tmp == "/") {
        tmp.clear();
    }
    return tmp + req.result;
}

quint64 ServiceManager::queryFileID(const QString &uuid, const QString &filePath)
{
    if (!m_udevClient) {
        sendErrorReply(QDBusError::Failed, "is service not setuped?");
        return 0;
    }

    if (m_mountpointsCache.isEmpty()) {
        sendErrorReply(QDBusError::Failed, "is service not setuped correctlly?");
        return 0;
    }

    if (filePath.isEmpty() || !filePath.startsWith("/")) {
        sendErrorReply(QDBusError::InvalidArgs, QString("invalid argument %1").arg(filePath));
        return 0;
    }

    struct stat file_stat;
    int ret = stat(filePath.toLocal8Bit().constData(), &file_stat);
    if (ret != 0) {
        sendErrorReply(QDBusError::Failed, QString("can not query %1 info").arg(filePath));
        return 0;
    }
    quint64 inoNum = file_stat.st_ino;
    if (uuid.isEmpty()) {
        dev_t device_id = file_stat.st_dev;  // 文件所在设备的ID
        dev_t real_device_id = MKDEV_KERNEL(MAJOR(device_id), MINOR(device_id));
        auto tmpUUID = m_devCache.value(real_device_id).first;
        auto id = FileIDMapper::instance()->getFileIDByInode(tmpUUID, inoNum, true);
        return id;
    } else {
        auto id = FileIDMapper::instance()->getFileIDByInode(uuid.toStdString(), inoNum, true);
        return id;
    }
}

void ServiceManager::test(QString msg)
{
    if (msg == "quit") {
        for (auto connection : m_connections) {
            delete connection;
        }
        m_connections.clear();
        exit(0);
    }
    handleFileEventV1(msg);
}

void ServiceManager::test2(const QByteArray &buffer)
{
    handleFileEventFromProtobuf(buffer);
}

void ServiceManager::handleMountEvent(uint32_t pid, std::string command, bool isMount, std::string devName, uint64_t devNum)
{
    if (!m_udevClient) {
        return;
    }
    if (isMount) {
        // 收到mount added事件时，文件系统可能还未就绪，这里添加200ms延时确保能够正常获取挂载点信息
        QTimer::singleShot(200, this, [=, this]{
            qDebug() << "mount added" << devNum;
            auto deviceFile = devName.c_str();
            if (deviceFile) {
                g_autoptr (GUdevDevice) device = g_udev_client_query_by_device_file(m_udevClient, deviceFile);
                if (!device) {
                    qCritical() << "can not query device info for" << QString::fromStdString(devName);
                    return;
                }
                // note：mount add事件需要使用核外dev构造devNum，否则会覆盖挂载点所在设备的信息
                auto tmp_dev_num = g_udev_device_get_device_number(device);
                auto realDevNum = MKDEV_KERNEL(MAJOR(tmp_dev_num), MINOR(tmp_dev_num));
                const char *uuid = g_udev_device_get_property(device, "ID_FS_UUID");
                g_autofree char* res = get_mountpoint_by_device(deviceFile);
                qDebug() << uuid << res;
                std::string mountRootPath;
                // 如果是根目录，则设置挂载路径为空字符串，避免拼接出错。
                if (!res) {
                    return;
                } else if (g_str_equal(res, "/")) {
                    mountRootPath = "";
                } else {
                    QString str(res);
                    mountRootPath = str.toStdString();
                }
                qDebug() << "init database" << uuid << realDevNum << res;
                auto mapper = FileIDMapper::instance();
                mapper->initDataBase(uuid);
                qDebug() << "init database done";
                std::cout << "start insert dev cache" << realDevNum << uuid << mountRootPath << std::endl;
                m_devCache.insert(realDevNum, std::make_pair(uuid, mountRootPath));
                m_mountpointsCache.insert(QString::fromStdString(uuid), std::make_pair(mountRootPath, realDevNum));
                std::cout << "insert dev cache done" << std::endl;
            } else {
                qDebug() << "ignore mount added" << devNum;
            }
        });
    } else {
        // 如果设备还存在，则跳过
        g_autoptr (GUdevDevice) device = g_udev_client_query_by_device_file(m_udevClient, devName.c_str());
        if (device) {
            return;
        }
        qDebug() << "mount removed" << QString::fromStdString(devName) << devNum;
        for (auto value : m_devCache.values()) {
            qDebug() << QString::fromStdString(value.first) << QString::fromStdString(value.second);
        }
        qDebug() << "=========";
        auto key = m_devCache.take(devNum).first;
        m_mountpointsCache.remove(QString::fromStdString(key));
        //m_devCache.remove(devNum);
    }
}

void ServiceManager::handleFileEventV1(QString event)
{
    for (auto connection : m_connections) {
        connection->writeMessage(event);
    }
}

void ServiceManager::handleFileEventFromProtobuf(const QByteArray &buffer)
{
    for (auto connection : m_connections) {
        connection->writeBuffer(buffer);
    }
}

void ServiceManager::handleFileEventV2(uint32_t pid, std::string command, uint32_t eventType, uint64_t srcDev, uint64_t srcIno, std::string srcPath, uint64_t dstDev, uint64_t dstIno, std::string dstPath)
{
    if (!m_udevClient) {
        return;
    }

//    qDebug() << __FUNCTION__ << pid << QString::fromStdString(command) << eventType <<
//                srcDev << MAJOR_KERNEL(srcDev) << MINOR_KERNEL(srcDev) << srcIno << QString::fromStdString(srcPath) <<
//                dstDev << MAJOR_KERNEL(dstDev) << MINOR_KERNEL(dstDev) << dstIno << QString::fromStdString(dstPath);

    std::string srcUUID = m_devCache[srcDev].first;
    std::string dstUUID = m_devCache[dstDev].first;
    std::string srcFullPath = m_devCache[srcDev].second + srcPath;
    std::string dstFullPath = m_devCache[dstDev].second + dstPath;
    uint64_t srcId = 0;
    uint64_t dstId = 0;
    bool shouldSendFileEvent = false;

    if (!google::protobuf::internal::WireFormatLite::VerifyUtf8String(srcFullPath.data(), srcFullPath.length(), google::protobuf::internal::WireFormatLite::Operation::SERIALIZE, nullptr)) {
        qWarning() << "can not verify" << QString::fromStdString(srcFullPath) << "as utf-8 string, skip this event";
        return;
    }

    // 处理文件id和inode映射关系
    auto mapper = FileIDMapper::instance();
    switch (eventType) {
    case MOUNT_ADDED: {
        qDebug() << "mount added" << MAJOR_KERNEL(srcDev) << MINOR_KERNEL(srcDev);
        return;
    }
    case MOUNT_REMOVED: {
        qDebug() << "mount removed" << MAJOR_KERNEL(srcDev) << MINOR_KERNEL(srcDev);
        return;
    }
    case FILE_CREATE: {
        shouldSendFileEvent = true;
        srcId = mapper->addFile(srcUUID, srcIno);
        break;
    }
    case FILE_DELETE: {
        shouldSendFileEvent = true;
        if (srcIno) {
            srcId = mapper->getFileIDByInode(srcUUID, srcIno);
            mapper->deleteFile(srcUUID, srcIno);
        }
        break;
    }
    case FILE_RENAME: {
        shouldSendFileEvent = true;
        qDebug() << "file rename event" << srcIno << QString::fromStdString(srcFullPath) << dstIno << QString::fromStdString(dstFullPath);
        if (dstIno && (srcIno != dstIno)) {
            qDebug() << "rename: real event is content change";
            // 实际为content change事件，如pluma保存文件
            eventType = FILE_CONTENT_CHANGED;
            auto id = mapper->getFileIDByInode(srcUUID, srcIno);
            mapper->deleteFile(dstUUID, srcIno);
            mapper->changeFileInode(dstUUID, id, dstIno);
            srcId = id;
            dstId = 0;
            srcFullPath = dstFullPath;
            dstFullPath = "";
            dstDev = 0;
        } else {
            qDebug() << "rename: real event is rename";
            // 获取inode对应的id,如果没有则分配一个
            auto id = mapper->getFileIDByInode(srcUUID, srcIno);
            if (!id) {
                id = mapper->addFile(srcUUID, srcIno);
            }
            srcId = id;
            dstId = id;
        }
        break;
    }
    case FILE_CONTENT_CHANGED: {
        shouldSendFileEvent = true;
        // 如果之前没有建立id映射，则添加id映射
        auto id = mapper->getFileIDByInode(srcUUID, srcIno);
        if (!id) {
            id = mapper->addFile(srcUUID, srcIno);
        }
        srcId = id;
        break;
    }
    case FILE_PERM_CHANGE: {
        shouldSendFileEvent = true;
        // 如果之前没有建立id映射，则添加id映射
        auto id = mapper->getFileIDByInode(srcUUID, srcIno);
        if (!id) {
            id = mapper->addFile(srcUUID, srcIno);
        }
        srcId = id;
        break;
    }
    default:
        return;
    }

    if (!shouldSendFileEvent)
        return;

    if (srcUUID.length() < 2) {
        return;
    }

    if (FileIDMapper::filterFileName(srcFullPath)) {
        return;
    }

    if (FileIDMapper::filterCommand(command)) {
        return;
    }

    if (eventType != FILE_DELETE && !srcId) {
        qDebug() << "can not get valid id for" << QString::fromStdString(srcFullPath) << ", skip this event";
        return;
    }

    FileEvent event;
    event.set_pid(pid);
    auto acommand = new std::string(command);
    event.set_allocated_command(acommand);
    event.set_eventtype(eventType);
    event.set_srcdev(srcDev);
    event.set_srcino(srcId);
    auto asrcpath = new std::string(srcFullPath);
    event.set_allocated_srcpath(asrcpath);
    event.set_dstdev(dstDev);
    event.set_dstino(dstId);
    auto adstpath = new std::string(dstFullPath);
    event.set_allocated_dstpath(adstpath);
    auto asrcUUID = new std::string(srcUUID);
    event.set_allocated_srcuuid(asrcUUID);
    auto adstUUID = new std::string(dstUUID);
    event.set_allocated_dstuuid(adstUUID);

    qDebug() << "===========send protobuf message===========" <<
                "pid:" << event.pid() << "command:" << QString::fromStdString(event.command()) << "type:" << event.eventtype() << "\n" <<
                "src dev:" << event.srcdev() << "src id:" << event.srcino() << "src fullpath:" << QString::fromStdString(event.srcpath()) << "\n" <<
                "dst dev:" << event.dstdev() << "dst id:" << event.dstino() << "dst fullpath:" << QString::fromStdString(event.dstpath());

    QByteArray buffer;
    buffer.resize(event.ByteSizeLong());
    event.SerializePartialToArray(buffer.data(), buffer.size());

    handleFileEventFromProtobuf(buffer);
}
